/*
 * Copyright (C) 2012 The MjSip Open Source Project Copyright (C) 2005 Luca Veltri - University of Parma - Italy
 * 
 * This file is part of MjSip (http://www.mjsip.org)
 * 
 * Sipdroid is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
 * 
 * This source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this source code; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

package com.cvtt.sip;

import java.util.Vector;
import android.util.Log;

import com.zoolu.sip.address.NameAddress;
import com.zoolu.sip.authentication.DigestAuthentication;
import com.zoolu.sip.header.AuthorizationHeader;
import com.zoolu.sip.header.ContactHeader;
import com.zoolu.sip.header.ExpiresHeader;
import com.zoolu.sip.header.Header;
import com.zoolu.sip.header.ProxyAuthenticateHeader;
import com.zoolu.sip.header.ProxyAuthorizationHeader;
import com.zoolu.sip.header.RequestLine;
import com.zoolu.sip.header.StatusLine;
import com.zoolu.sip.header.ViaHeader;
import com.zoolu.sip.header.WwwAuthenticateHeader;
import com.zoolu.sip.message.Message;
import com.zoolu.sip.message.MessageFactory;
import com.zoolu.sip.message.SipMethods;
import com.zoolu.sip.provider.SipProvider;
import com.zoolu.sip.transaction.TransactionClient;
import com.zoolu.sip.transaction.TransactionClientListener;
import com.zoolu.tools.ExceptionPrinter;
import com.zoolu.tools.LogLevel;
import com.zoolu.tools.MJLog;

/**
 * Register User Agent. It registers (one time or periodically) a contact address with a registrar server.
 */
public class RegisterAgent implements TransactionClientListener {
	/** Max number of registration attempts. */
	static final int MAX_ATTEMPTS = 3;

	/* States for the RegisterAgent Module */
	public static final int UNREGISTERED = 0;
	public static final int REGISTERING = 1;
	public static final int REGISTERED = 2;
	public static final int DEREGISTERING = 3;

	/** RegisterAgent listener */
	RegisterAgentListener listener;

	/** SipProvider */
	SipProvider sip_provider;

	/** User's URI with the fully qualified domain name of the registrar server. */
	NameAddress target;

	/** User name. */
	String username;

	/** User realm. */
	String realm;

	/** User's passwd. */
	String passwd;

	/** Q value for this registration (added by mandrajg) */
	String qvalue;

	/**
	 * IMS Communication Service Identifier for this registration (currently only one supported)(added by mandrajg)
	 */
	String icsi;
	
	/**
	 * device_guid  for kickout other app (added by zhh_li 2015-11-27 for kickout)
	 */
	String device_guid;

	/** Nonce for the next authentication. */
	String next_nonce;

	/** Qop for the next authentication. */
	String qop;

	/** User's contact address. */
	NameAddress contact;

	/** Expiration time. */
	int expire_time;

	/** Whether keep on registering. */
	boolean loop;

	/** Event logger. */
	MJLog log;

	/** Number of registration attempts. */
	int attempts, subattempts;

	/** Current State of the registrar component */
	int CurrentState = UNREGISTERED;

	SIPConfig sipConfig;

	// SubscriberDialog sd;
	boolean alreadySubscribed = true;
	Message currentSubscribeMessage;
	public final int SUBSCRIPTION_EXPIRES = 18400000;

	// TODO:Added by huah in 2012-03-28 for debug
	Message lastReq;

	boolean rdsOK = false;

	/**
	 * Creates a new RegisterAgent with authentication credentials (i.e. username, realm, and passwd).
	 */
	public RegisterAgent(SipProvider sip_provider, String target_url, String contact_url, String username, String realm, String passwd, RegisterAgentListener listener, SIPConfig sipConfig,
			String qvalue, String icsi, String device_guid) { // modified by mandrajg zhh_li

		init(sip_provider, target_url, contact_url, listener);

		// authentication specific parameters
		this.username = username;
		this.realm = realm;
		this.passwd = passwd;
		this.sipConfig = sipConfig;

		// IMS specifics (added by mandrajg)
		this.qvalue = qvalue;
		this.icsi = icsi;

	}

	public void halt() {
		this.listener = null;
		this.username = null;
		this.realm = null;
		this.passwd = null;
		this.next_nonce = null;
		this.qop = null;
		this.attempts = 0;
		this.lastReq = null;
		CurrentState = UNREGISTERED;
	}

	/** Inits the RegisterAgent. */
	private void init(SipProvider sip_provider, String target_url, String contact_url, RegisterAgentListener listener) {

		this.listener = listener;
		this.sip_provider = sip_provider;
		this.log = sip_provider.getLog();
		this.target = new NameAddress(target_url);
		this.contact = new NameAddress(contact_url);
		// this.expire_time = SipStack.default_expires;
		this.expire_time = 30;

		// authentication
		this.username = null;
		this.realm = null;
		this.passwd = null;
		this.next_nonce = null;
		this.qop = null;
		this.attempts = 0;

		// TODO:Added by huah in 2012-03-28 for debug
		this.lastReq = null;
	}

	/** Whether it is periodically registering. */
	public boolean isRegistered() {
		return (CurrentState == REGISTERED || CurrentState == REGISTERING);
	}

	/** Registers with the registrar server. */
	public boolean register() {
		attempts = 0;
		printLog("register()...", LogLevel.MEDIUM);
/*		if (NetworkUtil.isAvailable() == false) {
			printLog("NetUtil.checkNetwork() return false", LogLevel.MEDIUM);
			if (listener != null) {
				listener.onRegisterFailure(503);
			}
			return false;
		}*/
		// TODO:Added by huah in 2012-03-28 for debug
		if (isRegistered() && lastReq != null) {
			printLog("register-->regRefresh()", LogLevel.MEDIUM);
			return regRefresh();
		}
		printLog("register-->register(30)", LogLevel.MEDIUM);
		return register(expire_time);
	}

	TransactionClient t;

	// TODO:Added by huah in 2012-03-28 for debug
	public boolean regRefresh() {
		printLog("regRefresh()...", LogLevel.MEDIUM);
		/*if (NetworkUtil.isAvailable() == false) {
			printLog("NetUtil.checkNetwork() return false", LogLevel.MEDIUM);
			return false;
		}*/
		attempts = 0;
		CurrentState = REGISTERING;
		// if (lastReq.hasAuthorizationHeader())
		// lastReq.removeAuthorizationHeader();
		lastReq.setCSeqHeader(lastReq.getCSeqHeader().incSequenceNumber());
		t = new TransactionClient(sip_provider, lastReq, this, 30000);
		t.request();
		SIPEngine.getSIPEngine().addLogInfo("a refresh register:" + lastReq.getRequestLine().toString());
		return true;
	}

	/** Registers with the registrar server for <i>expire_time</i> seconds. */
	// Modified by huah in 2012-08-28
	public synchronized boolean register(int expire_time) {
		printLog("register(30)...", LogLevel.MEDIUM);
		/*if (NetworkUtil.isAvailable() == false) {
			printLog("NetUtil.checkNetwork() return false", LogLevel.MEDIUM);
			return false;
		}*/
		attempts = 0;
		if (expire_time > 0) {
			// Update this to be the default registration duration for next
			// instances as well.

			if (CurrentState == DEREGISTERING) {
				if (t != null)
					t.terminate();
				CurrentState = UNREGISTERED;
			}
			// if (CurrentState != UNREGISTERED && CurrentState != REGISTERED) {
			// printLog("register(30):CurrentState is wrong,return false",LogLevel.MEDIUM);
			// return false;
			// }
			this.expire_time = expire_time;
			CurrentState = REGISTERING;
		}
		else {
			if (CurrentState == REGISTERING) {
				if (t != null)
					t.terminate();
			}
			expire_time = 0;
			CurrentState = DEREGISTERING;
		}

		// Create message re (modified by mandrajg)
		Message req = MessageFactory.createRegisterRequest(sip_provider, target, target, new NameAddress(sipConfig.contact_url), qvalue, icsi,device_guid);

		req.setExpiresHeader(new ExpiresHeader(String.valueOf(expire_time)));

		if (SIPConfig.LOG_ENABLE) {
			System.out.println("============================\r\n");
			System.out.println("onUaRegistration:" + req.getCallIdHeader().getCallId());
			System.out.println("\r\n===========================");
		}

		// create and fill the authentication params this is done when
		// the UA has been challenged by the registrar or intermediate UA
		if (next_nonce != null) {
			AuthorizationHeader ah = new AuthorizationHeader("Digest");

			ah.addUsernameParam(username);
			ah.addRealmParam(realm);
			ah.addNonceParam(next_nonce);
			ah.addUriParam(req.getRequestLine().getAddress().toString());
			ah.addQopParam(qop);
			String response = (new DigestAuthentication(SipMethods.REGISTER, ah, null, passwd)).getResponse();
			ah.addResponseParam(response);
			req.setAuthorizationHeader(ah);
		}

		if (expire_time > 0) {
			printLog("Registering contact " + contact + " (it expires in " + expire_time + " secs)", LogLevel.HIGH);
		}
		else {
			printLog("Unregistering contact " + contact, LogLevel.HIGH);
		}

		t = new TransactionClient(sip_provider, req, this, 30000);
		t.request();

		SIPEngine.getSIPEngine().addLogInfo("a new register:" + req.getRequestLine().toString());

		return true;
	}

	/** Unregister with the registrar server */
	// Modified by huah in 2012-11-23
	public boolean unregister() {

		/*if (!NetworkUtil.isAvailable()) {
			attempts = 0;
			CurrentState = UNREGISTERED;
			lastReq = null;
			if (t != null)
				t.terminate();
			return true;
		}*/

		if (lastReq != null) {
			attempts = 0;
			CurrentState = DEREGISTERING;
			// if (lastReq.hasAuthorizationHeader())
			// lastReq.removeAuthorizationHeader();
			lastReq.setCSeqHeader(lastReq.getCSeqHeader().incSequenceNumber());
			lastReq.setExpiresHeader(new ExpiresHeader(0));
			t = new TransactionClient(sip_provider, lastReq, this, 30000);
			t.request();
			lastReq = null;
			return true;
		}
		return register(0);
	}

	// **************** Transaction callback functions *****************

	/** Callback function called when client sends back a failure response. */

	/** Callback function called when client sends back a provisional response. */
	public void onTransProvisionalResponse(TransactionClient transaction, Message resp) { // do nothing..
	}

	/** Callback function called when client sends back a success response. */
	public void onTransSuccessResponse(TransactionClient transaction, Message resp) {
		if (transaction.getTransactionMethod().equals(SipMethods.REGISTER)) {

			if (resp.hasAuthenticationInfoHeader()) {
				next_nonce = resp.getAuthenticationInfoHeader().getNextnonceParam();
			}

			StatusLine status = resp.getStatusLine();
			String result = status.getCode() + " " + status.getReason();

			int expires = 0;
			if (resp.hasExpiresHeader()) {
				expires = resp.getExpiresHeader().getDeltaSeconds();
			}
			else if (resp.hasContactHeader()) {
				Vector<Header> contacts = resp.getContacts().getHeaders();
				for (int i = 0; i < contacts.size(); i++) {
					int exp_i = (new ContactHeader((Header) contacts.elementAt(i))).getExpires();
					if (exp_i > 0 && (expires == 0 || exp_i < expires))
						expires = exp_i;
				}
			}

			printLog("Registration success: " + result, LogLevel.HIGH);

			if (isRegistered()) {
				CurrentState = REGISTERED;
				if (listener != null) {
					sipConfig.refresh_time = expires;
					lastReq = transaction.getRequestMessage();
					listener.onRegisterSuccess();
				}
			}
			else {
				CurrentState = UNREGISTERED;
			}
		}
	}

	/** Callback function called when client sends back a failure response. */
	public void onTransFailureResponse(TransactionClient transaction, Message resp) {
		if (transaction.getTransactionMethod().equals(SipMethods.REGISTER)) {
			StatusLine status = resp.getStatusLine();
			int code = status.getCode();

			SIPEngine.getSIPEngine().addLogInfo("recv:" + code);

			if (code == 301) {
				rdsOK = true;
				if (attempts < MAX_ATTEMPTS) {
					attempts++;
					Message req = transaction.getRequestMessage();
					req.setCSeqHeader(req.getCSeqHeader().incSequenceNumber());

					String contact = resp.getContactHeader().getNameAddress().getAddress().toString();

					RequestLine reqLine = new RequestLine(req.getRequestLine().getMethod(), contact);

					req.setRequestLine(reqLine);

					t = new TransactionClient(sip_provider, req, this, 30000);

					t.request();

					SIPEngine.getSIPEngine().addLogInfo("send register to new server:" + reqLine.toString());

					return;
				}
				else {
					SIPEngine.getSIPEngine().addLogInfo("recv 301 again,but retry so much,so report error.");
					if (isRegistered()) {
						if (listener != null) {
							listener.onRegisterFailure(code);
						}
					}
				}
			}
			else {
				if (code == 401 || code == 407) {
					if (processAuthenticationResponse(transaction, resp, code))
						return;
				}

				String result = code + " " + status.getReason();

				if (isRegistered()) {
					CurrentState = UNREGISTERED;
					if (listener != null) {
						if (rdsOK)
							listener.onRegisterFailure(code);
						else
							listener.onRetryRegister(code);
					}
				}
				else {
					CurrentState = UNREGISTERED;
				}

				printLog("Registration failure: " + result, LogLevel.HIGH);
			}
		}

	}

	private boolean generateRequestWithProxyAuthorizationheader(Message resp, Message req) {
		if (resp.hasProxyAuthenticateHeader() && resp.getProxyAuthenticateHeader().getRealmParam().length() > 0) {
			sipConfig.realm = realm = resp.getProxyAuthenticateHeader().getRealmParam();
			ProxyAuthenticateHeader pah = resp.getProxyAuthenticateHeader();
			String qop_options = pah.getQopOptionsParam();

			printLog("DEBUG: qop-options: " + qop_options, LogLevel.MEDIUM);

			qop = (qop_options != null) ? "auth" : null;

			ProxyAuthorizationHeader ah = (new DigestAuthentication(req.getTransactionMethod(), req.getRequestLine().getAddress().toString(), pah, qop, null, username, passwd))
					.getProxyAuthorizationHeader();
			req.setProxyAuthorizationHeader(ah);

			return true;
		}
		return false;
	}

	private boolean generateRequestWithWwwAuthorizationheader(Message resp, Message req) {
		if (resp.hasWwwAuthenticateHeader() && resp.getWwwAuthenticateHeader().getRealmParam().length() > 0) {
			sipConfig.realm = realm = resp.getWwwAuthenticateHeader().getRealmParam();
			WwwAuthenticateHeader wah = resp.getWwwAuthenticateHeader();
			String qop_options = wah.getQopOptionsParam();

			printLog("DEBUG: qop-options: " + qop_options, LogLevel.MEDIUM);

			qop = (qop_options != null) ? "auth" : null;

			AuthorizationHeader ah = (new DigestAuthentication(req.getTransactionMethod(), req.getRequestLine().getAddress().toString(), wah, qop, null, username, passwd)).getAuthorizationHeader();
			req.setAuthorizationHeader(ah);
			return true;
		}
		return false;
	}

	private boolean handleAuthentication(int respCode, Message resp, Message req) {
		switch (respCode) {
			case 407:
				return generateRequestWithProxyAuthorizationheader(resp, req);
			case 401:
				return generateRequestWithWwwAuthorizationheader(resp, req);
		}
		return false;
	}

	private boolean processAuthenticationResponse(TransactionClient transaction, Message resp, int respCode) {
		if (attempts < MAX_ATTEMPTS) {
			attempts++;
			Message req = transaction.getRequestMessage();
			req.setCSeqHeader(req.getCSeqHeader().incSequenceNumber());
			ViaHeader vh = req.getViaHeader();
			String newbranch = SipProvider.pickBranch();
			vh.setBranch(newbranch);
			req.removeViaHeader();
			req.addViaHeader(vh);

			if (handleAuthentication(respCode, resp, req)) {
				t = new TransactionClient(sip_provider, req, this, 30000);
				t.request();
				SIPEngine.getSIPEngine().addLogInfo("after recv 401,send new register with authinfo:" + req.getRequestLine().toString());
				return true;
			}
		}
		return false;
	}

	/** Callback function called when client expires timeout. */
	public void onTransTimeout(TransactionClient transaction) {
		if (transaction == null)
			return;
		if (transaction.getTransactionMethod().equals(SipMethods.REGISTER)) {
			printLog("Registration failure: No response from server.", LogLevel.HIGH);

			// Since the transactions are atomic, we rollback to the
			// previous state

			if (isRegistered()) {
				CurrentState = UNREGISTERED;

				if (listener != null) {
					if (rdsOK)
						listener.onRegisterFailure(408);
					else
						listener.onRetryRegister(408);
				}
			}
			else {
				CurrentState = UNREGISTERED;

			}
		}
	}

	// ****************************** Logs *****************************
	/** Adds a new string to the default Log */
	void printLog(String str, int level) {
		if (SIPConfig.LOG_ENABLE == false)
			return;
		Log.d("RegisterAgent", str);
		// if (log != null)
		// log.println("RegisterAgent: " + str, level + SipStack.LOG_LEVEL_UA);
		if (level <= LogLevel.HIGH)
			System.out.println("RegisterAgent: " + str);
	}

	/** Adds the Exception message to the default Log */
	void printException(Exception e, int level) {
		if (SIPConfig.LOG_ENABLE == false)
			return;
		Log.e("RegisterAgent", "Exception: " + ExceptionPrinter.getStackTraceOf(e));
		// if (log != null)
		// log.printException(e, level + SipStack.LOG_LEVEL_UA);
	}

}
